package com.mvc.framework.servlet;

import com.mvc.framework.annotation.GPController;
import com.mvc.framework.annotation.GPRequestMapping;
import com.mvc.framework.annotation.GPRequestParam;
import com.mvc.framework.context.GPApplicationContext;
import com.sun.deploy.net.HttpResponse;
import com.sun.xml.internal.ws.api.server.Adapter;
import lombok.Data;
import org.omg.PortableServer.RequestProcessingPolicy;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GPDispatcherServlet extends HttpServlet {

    private static final String LOCATION = "contextConfigLocation";

    private List<Handler> handlerMapping = new ArrayList<Handler>();
    private Map<Handler,HandlerAdapter> adapterMapping=new HashMap<Handler,HandlerAdapter>();

    private List<ViewResolver> viewResolvers =new ArrayList<>();
    /**
     * 初始化容器
     * @param config
     * @throws ServletException
     */
    //初始化IOC容器
    @Override
    public void init(ServletConfig config) throws ServletException {

        //IOC容器必须要先初始化
        //假装容器已启动
        GPApplicationContext context =new GPApplicationContext(config.getInitParameter(LOCATION));

        //提供的文件上传解析器的接口
        initMultipartResolver(context);
        //多语言、国际化
        initLocaleResolver(context);
        //主题View层的
        initThemeResolver(context);
        //============== 重要 ================
        //解析url和Method的关联关系
        initHandlerMappings(context);
        //适配器（匹配的过程）
        initHandlerAdapters(context);

        //异常解析
        initHandlerExceptionResolvers(context);
        //视图转发（根据视图名字匹配到一个具体模板）
        initRequestToViewNameTranslator(context);

        //解析模板中的内容（拿到服务器传过来的数据，生成HTML代码）
        initViewResolvers(context);

        initFlashMapManager(context);

        System.out.println("GPSpring MVC is init.");

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req,resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Exception, Msg :" + Arrays.toString(e.getStackTrace()));
        }
    }
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{

        //先取出来一个Handler，从HandlerMapping取
        Handler handler = getHandler(req);
        if(handler==null){
            resp.getWriter().write("404 NOT FOUND");
        }
        HandlerAdapter handlerAdapter = getHandlerAdapt(handler);
        GPModelAndView mv =handlerAdapter.handle(req,resp,handler);
        //写一个咕泡模板框架
        //Veloctiy #
        //Freemark  #
        //JSP   ${name}

        //咕泡模板   @{name}
        applyDefaultViewName(resp, mv);




    }

    private void applyDefaultViewName(HttpServletResponse resp, GPModelAndView mv) throws IOException {
        if(null==mv) {
            return;
        }
        if(viewResolvers.isEmpty()){ return;}

        for(ViewResolver resolver:viewResolvers) {
            if(!resolver.getView().equals(mv.getView())) {continue; }

            String r = resolver.parse(mv);
            if(r!=null) {
                resp.getWriter().write(r);
            }

        }
    }

    private HandlerAdapter getHandlerAdapt(Handler handler) {
        if(adapterMapping.isEmpty()){
            return null;
        }
        HandlerAdapter handlerAdapter =adapterMapping.get(handler);
        return handlerAdapter;
    }

    private Handler getHandler(HttpServletRequest req) {

        if (handlerMapping.isEmpty()){
            return null;
        }
        String url =req.getRequestURI();
        String contextPath=req.getContextPath();

        url = url.replace(contextPath,"").replaceAll("/+","/");

        for (Handler handler :handlerMapping){
            Matcher matcher =handler.pattern.matcher(url);
            if(!matcher.matches()){
                continue;
            }
            return handler;
        }
        return null;

    }


    private void initFlashMapManager(GPApplicationContext context) { }

    //解析模板中的内容（拿到服务器传过来的数据，生成HTML代码）
    private void initViewResolvers(GPApplicationContext context) {
        //模板一般是不会放到WebRoot下的，而是放在WEB-INF下，或者classes下
        //这样就避免了用户直接请求到模板
        //加载模板的个数，存储到缓存中
        //检查模板中的语法错误
        String templateRoot =context.getConfig().getProperty("templateRoot");
        File rootFile = new File(this.getClass().getClassLoader().getResource(templateRoot).getFile());

        for(File template:rootFile.listFiles()){
            viewResolvers.add(new ViewResolver(template.getName(),template));
        }

    }
    //异常解析
    private void initHandlerExceptionResolvers(GPApplicationContext context){}
    //视图转发（根据视图名字匹配到一个具体模板）
    private void initRequestToViewNameTranslator(GPApplicationContext context){}

    private void initHandlerAdapters(GPApplicationContext context) {
        if(handlerMapping.isEmpty()){
            return;
        }

        for(Handler handler :handlerMapping){
            Map<String,Integer> paramMapping =new HashMap<String, Integer>();
            Class<?>[] parameterTypes =handler.method.getParameterTypes();

            for(int i=0;i<parameterTypes.length;i++){
                Class<?> type =parameterTypes[i];

                if(type== HttpServletRequest.class || type== HttpServletResponse.class){
                    paramMapping.put(type.getName(),i);
                }
            }
            Annotation[][] pa =handler.getMethod().getParameterAnnotations();
            for(int i=0;i<pa.length;i++){
                for(Annotation a:pa[i]){
                    if(a instanceof GPRequestParam){
                        String paramName=((GPRequestParam) a).value();
                        paramMapping.put(paramName,i);
                    }

                }
            }
            adapterMapping.put(handler,new HandlerAdapter(paramMapping));
        }
    }

    public class ViewResolver{
        private String view;
        private File file;
        protected ViewResolver(String view,File file){
            this.view = view;
            this.file = file;
        }
        public String getView() {
            return view;
        }

        public String parse(GPModelAndView mv) throws IOException {

            StringBuffer sb = new StringBuffer();

            RandomAccessFile ra = new RandomAccessFile(this.file, "r");

            String line = null;
            try {
                while (null != (line = ra.readLine())) {
                    Matcher m = matcher(line);
                    while (m.find()) {
                        for (int i = 1; i <= m.groupCount(); i++) {
                            String paramName = m.group(i);
                            Object paramValue = mv.getModel().get(paramName);
                            if (null == paramValue) {
                                continue;
                            }
                            line = line.replaceAll("@\\{" + paramName + "\\}", paramValue.toString());
                        }
                    }
                    sb.append(line);
                }
            } finally {

                return sb.toString();

            }
        }

            public Matcher matcher(String str) {
                Pattern pattern =Pattern.compile("@\\{(.+?)\\}",Pattern.CASE_INSENSITIVE);
                return pattern.matcher(str);
            }
        }

        private void initHandlerMappings(GPApplicationContext context) {
            Map<String, Object> ioc =context.getAll();

            if(ioc.isEmpty()){
                return;
            }

            for(Map.Entry<String, Object> entry : ioc.entrySet()){
                Class<?> clazz = entry.getValue().getClass();
                if(!clazz.isAnnotationPresent(GPController.class)){ continue; }
                String url = "";
                if(clazz.isAnnotationPresent(GPRequestMapping.class)){
                    GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);
                    url=requestMapping.value();
                }
                //扫描Controller下面的所有的方法
                Method[] methods =clazz.getMethods();
                for(Method method :methods){
                    if(!method.isAnnotationPresent(GPRequestMapping.class)){continue; }
                    GPRequestMapping requestMapping =method.getAnnotation(GPRequestMapping.class);
                    String regex =url+requestMapping.value().replaceAll("/","/+");
                    Pattern pattern = Pattern.compile(regex);
                    handlerMapping.add(new Handler(pattern,entry.getValue(),method));
                    System.out.println("Mapping: " + regex + " " +  method.toString());
                }
            }




        }

        /**
         * HandlerMapping 定义
         * @author Tom
         *
         */
        @Data
        private class Handler{

            private Object controller;
            private Method method;
            private Pattern pattern;

            protected Handler(Pattern pattern,Object controller,Method method){
                this.pattern = pattern;
                this.controller = controller;
                this.method = method;
            }
        }
        private class HandlerAdapter{
            private Map<String,Integer> paramMapping;

            public HandlerAdapter(Map<String,Integer> paramMapping) {
                this.paramMapping=paramMapping;
            }
            //主要目的是用反射调用url对应的method

            private GPModelAndView handle(HttpServletRequest request, HttpServletResponse response,Handler handler) throws InvocationTargetException, IllegalAccessException {

                Class<?>[] paramTypes =handler.method.getParameterTypes();

                Object[] paramValues =new Object[paramTypes.length];

                Map<String,String[]> map =request.getParameterMap();

                for(Map.Entry<String, String[]> entry:map.entrySet()) {
                    if (!paramMapping.containsKey(entry.getKey())) {
                        continue;
                    }
                    String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
                    int index = paramMapping.get(entry.getKey());
                    paramValues[index] = castStringValue(paramTypes[index], value);
                }
                String requestName = HttpServletRequest.class.getName();
                if (paramMapping.containsKey(requestName)) {
                    int reqIndex = this.paramMapping.get(requestName);
                    paramValues[reqIndex] = request;
                }

                String responseName = HttpServletResponse.class.getName();
                if (paramMapping.containsKey(responseName)) {
                    int reqIndex = this.paramMapping.get(responseName);
                    paramValues[reqIndex] = response;
                }

                boolean isModelAndView= handler.method.getReturnType()==GPModelAndView.class;
                Object r=handler.method.invoke(handler.controller,paramValues);
                if(isModelAndView){
                    return (GPModelAndView) r;
                }else {
                    return null;
                }
            }
        }



    private Object castStringValue(Class<?> clazz,String value){
        if(clazz==String.class){
            return value;
        }else if(clazz==Integer.class){
            return Integer.valueOf(value);
        }else if(clazz == int.class){
            return Integer.valueOf(value).intValue();
        }else {
            return null;
        }


    }

    private void initThemeResolver(GPApplicationContext context) {
    }

    private void initLocaleResolver(GPApplicationContext context) {
    }

    private void initMultipartResolver(GPApplicationContext context) {
    }
}
